package com.kongjs.application.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kongjs.application.cache.UserCacheManager;
import com.kongjs.application.convert.MenusConverter;
import com.kongjs.application.enums.MenuType;
import com.kongjs.application.exception.AccountException;
import com.kongjs.application.exception.TokenException;
import com.kongjs.application.model.dto.*;
import com.kongjs.application.model.entity.LoginLog;
import com.kongjs.application.model.entity.Menus;
import com.kongjs.application.model.entity.Tenants;
import com.kongjs.application.model.entity.Users;
import com.kongjs.application.service.*;
import com.kongjs.application.util.IpUtil;
import com.kongjs.common.mybatis.tenant.TenantContextHolder;
import com.kongjs.common.mybatis.user.UserContextHolder;
import com.kongjs.common.rest.util.JsonUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.NumberUtils;
import org.springframework.util.ObjectUtils;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Transactional
@Service
public class AuthServiceImpl implements AuthService {
    @Resource
    private Executor applicationTaskExecutor;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private JwtService jwtService;
    @Resource
    private UserCacheManager userCacheManager;
    @Resource
    private ITenantService tenantService;
    @Resource
    private IUsersService usersIService;
    @Resource
    private IUserDetailService userDetailIService;
    @Resource
    private IUserRoleService userRoleService;
    @Resource
    private IRoleMenuService roleMenuService;
    @Resource
    private IMenusService menusService;
    @Resource
    private MenusConverter menusConverter;
    @Resource
    private ILoginLogService loginLogService;


    @Override
    public TokenDTO login(AccountDTO dto) {
        String tenantCode = dto.getTenantCode();
        String username = dto.getUsername();
        RLock lock = redissonClient.getLock("lock:login:" + tenantCode + "-" + username);
        LoginLog loginLog = new LoginLog();
        long start = System.currentTimeMillis();
        loginLog.setPlatform(dto.getPlatform());
        loginLog.setBrowser(dto.getBrowser());
        loginLog.setLoginTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(start), ZoneId.systemDefault()));
        try {
            if (!lock.tryLock(15L, 60L, TimeUnit.SECONDS)) {
                throw new AccountException("注册请求处理中，请稍后重试");
            }
            if (userCacheManager.notExistsTenant(tenantCode)) {
                throw new AccountException("租户不存在");
            }
            TenantCacheDTO tenantCache = userCacheManager.loadTenantCache(tenantCode);
            if (ObjectUtils.isEmpty(tenantCache.getEnabled()) || tenantCache.getEnabled().equals(0)) {
                throw new AccountException("租户未启用");
            }
            loginLog.setLoginMessage("租户：" + tenantCode + "用户：" + username);
            loginLog.setUserName(username);
            loginLog.setTenantId(tenantCache.getId());

            TenantContextHolder.setTenantId(String.valueOf(tenantCache.getId()));
            String password = dto.getPassword();
            Authentication auth = auth(username, password);

            UserDetailsDTO userDetailsDTO = (UserDetailsDTO) auth.getPrincipal();
            loginLog.setUserId(Long.valueOf(userDetailsDTO.getUserId()));
            JwtTokenDTO accessToken = jwtService.generateToken(userDetailsDTO, false);
            JwtTokenDTO refreshToken = jwtService.generateToken(userDetailsDTO, true);
            loginLog.setLoginResult("success");
            return new TokenDTO(accessToken.getToken(), refreshToken.getToken());
        } catch (InterruptedException e) {
            loginLog.setLoginResult("error");
            loginLog.setLoginException("系统中断");
            throw new AccountException("系统操作中断,请重试");
        } catch (Exception e) {
            loginLog.setLoginResult("error");
            loginLog.setLoginException(e.getMessage());
            throw e;
        } finally {
            loginLog.setLoginIpaddr(dto.getLoginIp());
            loginLog.setLoginIpinfo(IpUtil.getRegion(loginLog.getLoginIpaddr()));
            long end = System.currentTimeMillis();
            long ms = end - start;
            loginLog.setExecutionTime(ms);
            saveLoginLog(loginLog);
            TenantContextHolder.clearTenantId();
            UserContextHolder.clearUserId();
            // 释放锁（仅在当前线程持有锁时释放）
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public boolean register(AccountDTO dto) {
        String tenantCode = dto.getTenantCode();
        String username = dto.getUsername();
        RLock lock = redissonClient.getLock("lock:register:" + tenantCode + "-" + username);
        try {
            if (!lock.tryLock(15L, 60L, TimeUnit.SECONDS)) {
                throw new AccountException("注册请求处理中，请稍后重试");
            }
            if (userCacheManager.notExistsTenant(tenantCode)) {
                throw new AccountException("租户不存在");
            }
            TenantCacheDTO tenantCache = userCacheManager.loadTenantCache(tenantCode);
            if (ObjectUtils.isEmpty(tenantCache.getEnabled()) || tenantCache.getEnabled().equals(0)) {
                throw new AccountException("租户未启用");
            }
            if (userCacheManager.existsUser(username)) {
                throw new AccountException("该用户已存在");
            }
            String password = dto.getPassword();
            Users users = new Users();
            users.setTenantId(tenantCache.getId());
            users.setNickname(username);
            users.setUsername(username);
            users.setPassword(passwordEncoder.encode(password));
            boolean save = usersIService.save(users);
            TenantContextHolder.setTenantId(String.valueOf(tenantCache.getId()));
            UserContextHolder.setUserId(String.valueOf(users.getId()));
            return save;
        } catch (InterruptedException e) {
            throw new AccountException("系统操作中断,请重试");
        } catch (Exception e) {
            throw e;
        } finally {
            TenantContextHolder.clearTenantId();
            UserContextHolder.clearUserId();
            // 释放锁（仅在当前线程持有锁时释放）
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public void logout(TokenDTO dto) {

    }

    @Override
    public TokenDTO refresh(String refreshToken) {
        JwtTokenDTO accessToken = jwtService.refreshToken(refreshToken);
        return new TokenDTO(accessToken.getToken(), null);
    }


    @Override
    public UserInfoDTO info() {
        String userId = UserContextHolder.getUserId();
        Long id;
        try {
            id = NumberUtils.parseNumber(userId, Long.class);
        } catch (Exception ignored) {
            throw new TokenException("token错误");
        }
        UserCacheDTO userCache = userCacheManager.loadUserCache(id);
        if (ObjectUtils.isEmpty(userCache)) {
            return null;
        }
        TenantContextHolder.setTenantId(String.valueOf(userCache.getTenantId()));
        UserInfoDTO userInfo = new UserInfoDTO();
        userInfo.setAvatar(userCache.getAvatar());
        userInfo.setUserId(userCache.getId());
        userInfo.setUsername(userCache.getUsername());
        userInfo.setNickname(userCache.getNickname());
        userInfo.setHomePath(userCache.getHomePath());
        return userInfo;
    }

    @Override
    public List<TenantDTO> tenants() {
        List<Tenants> tenants = tenantService.lambdaQuery()
                .eq(Tenants::getEnabled, 1)
                .page(Page.of(1, 50))
                .getRecords();
        return tenants.stream().map(tenant -> {
            TenantDTO dto = new TenantDTO();
            dto.setTenantName(tenant.getTenantName());
            dto.setTenantCode(tenant.getTenantCode());
            return dto;
        }).toList();
    }

    @Override
    public List<QueryMenuDTO> menus() {
        String userId = UserContextHolder.getUserId();
        Long id;
        try {
            id = NumberUtils.parseNumber(userId, Long.class);
        } catch (Exception ignored) {
            throw new TokenException("token错误");
        }
        UserCacheDTO userCache = userCacheManager.loadUserCache(id);
        Set<Long> roleIds = userCache.getRoleIds();
        if (CollectionUtils.isEmpty(roleIds)) {
            return List.of();
        }
        Map<Long, RoleCacheDTO> roleCacheMap = userCacheManager.loadRoleCache(roleIds);
        if (CollectionUtils.isEmpty(roleCacheMap)) {
            return List.of();
        }
        Set<Long> menuIds = roleCacheMap.values().stream()
                .flatMap(dto -> dto.getMenuIds().stream())
                .collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(menuIds)) {
            return List.of();
        }
        List<Menus> menus = menusService.lambdaQuery()
                .in(Menus::getId, menuIds)
                .ne(Menus::getMenuType, MenuType.button.name())
                .orderByAsc(Menus::getMenuSort)
                .list();
        return menus.stream().map(entity -> {
            QueryMenuDTO dto = menusConverter.toDto(entity);
            QueryMenuMetaDTO metaDTO = JsonUtils.fromJson(entity.getMeta(), QueryMenuMetaDTO.class);
            dto.setMeta(metaDTO);
            return dto;
        }).toList();
    }

    /*
     * 验证
     * */
    public Authentication auth(String username, String password) {
        Authentication authenticationRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
        return this.authenticationManager.authenticate(authenticationRequest);
    }

    public void saveLoginLog(LoginLog loginLog) {
        applicationTaskExecutor.execute(() -> {
            try {
                transactionTemplate.executeWithoutResult((status) -> {
                    loginLogService.save(loginLog);
                });
            } catch (Exception e) {
                log.error("保存登录日志异常", e);
            }
        });
    }
}
